/*
 * \copyright Copyright 2013 Google Inc. All Rights Reserved.
 * \license @{
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @}
 */


#ifndef GOOGLEAPIS_SERVICE_MEDIA_UPLOADER_H_
#define GOOGLEAPIS_SERVICE_MEDIA_UPLOADER_H_

#include <memory>
#include <string>
using std::string;
#include "googleapis/client/transport/http_types.h"
#include "googleapis/client/util/status.h"
#include "googleapis/base/callback.h"
#include "googleapis/base/macros.h"
#include "googleapis/strings/stringpiece.h"
namespace googleapis {

namespace client {
class SerializableJson;
class DataReader;
class HttpRequest;

/*
 * Specifie the media upload interface for an endpoint.
 * @ingroup ClientServiceLayer
 *
 * The MediaUploadSpec captures the information in the discovery document
 * "MediaUpload/protocols" section for how to utilize a given protocol.
 *
 * The spec is given to a MediaUploader in order to configure it for the
 * current use case.
 *
 * This class only uses StringPiece for storage so that it can be used to
 * create instances as constant global or class variables.
 *
 * @see MediaUploader
 */
class MediaUploadSpec {
 public:
  /*
   * Constructs a default spec for hand-populating.
   */
  MediaUploadSpec();

  /*
   * Standard constructor for this spec information.
   *

   * @param[in] protocol Type type of media upload.
   *            "simple", "resuanble", "dirct"
   * @param[in] path_template The default path template to use for the
   *            upload if media upload were not actualy use.
   * @param[in] multipart True if multipart content types are supported.
   */
  MediaUploadSpec(
      const StringPiece& protocol,
      const StringPiece& path_template,
      bool multipart);

  /*
   * Standard destructor.
   */
  virtual ~MediaUploadSpec();

  /*
   * Returns the path template for the specification.
   */
  const StringPiece& path_template() const { return path_template_; }

  /*
   * Changes template path.
   */
  void set_path_template(const StringPiece& path_template) {
    path_template_ = path_template;
  }

  /*
   * Returns a standard protocol name like "simple" or "resumable".
   */
  const StringPiece& protocol() const { return protocol_; }

  /*
   * Changes the protocol used by this specification instance.
   */
  void set_protocol(const StringPiece& protocol) { protocol_ = protocol; }

  /*
   * Determine if multipart forms are supported by this specification or not.
   * @return if can perform multipart uploads, false if not.
   */
  bool is_multipart() const { return multipart_; }

  /*
   * Use multipart upload.
   */
  void set_multipart(bool multipart) { multipart_ = multipart; }

 private:
  StringPiece protocol_;
  StringPiece path_template_;
  bool multipart_;

  DISALLOW_COPY_AND_ASSIGN(MediaUploadSpec);
};

/*
 * Uploader for using Google media upload protocols.
 * @ingroup ClientServiceLayer
 *
 * In practice the methods generated by the code generator provide a factory
 * that creates one of these and you just populated it. The upload happens
 * when the method (acting as the factory) executes.
 *
 * <pre>
 *    std::unique_ptr<drive_api::File> file(drive_api::File::New());
 *    file->set_title(StrCat("Uploaded from ", path));
 *
 *    const DriveService::FilesResource& ops = app_->service()->get_files();
 *    std::unique_ptr<FilesResource_InsertMethod> insert(
 *        ops.NewInsertMethod(app_->credential()));
 *    client::MediaUploader* uploader = insert->media_uploader();
 *    uploader->set_metadata(*file);
 *    uploader->set_media_content(mime_type.as_string(), file_content);
 *    insert->Execute();
 * </pre>
 *
 * If using this yourself then instead of the insert->Execute you would call
 * uploader->upload, passing in the http_request to upload and http_response.
 * You can explicitly form the request yourself, or call BuildRequest to
 * initialize a standard one.
 *
 * The media uploader is not reusable at this time.
 *
 * TODO(user): Only a synchronous Upload is provided at this time
 * but eventually this will support asynchronous uploads. Even so, the
 * underlying HttpRequest and its HttpResponse attribute are threadsafe and
 * support condition variables so you can Upload in a thread and synchronize
 * on the response in another.
 *
 * TODO(user): The interface does not yet support
 *   -- streaming content
 *   -- resumable uploads
 *-- auto protocol selection
 * To add auto selection I'll probably configure it with a vector of specs to
 * choose from.
 */
class MediaUploader {
 public:
  /*
   * Callback function used to resolve URI templated variables in a URI
   * @param[in] StringPiece is the URI to resolve.
   * @param[out] The resolved URL
   * @return success if all the URI parameters could be resolved, failure
   *         if somec could not.
   */
  typedef ResultCallback2< googleapis::util::Status,    // Returns ok if fully prepared
                         const StringPiece&,  // URL to resolve
                         string*>             // resolved url
  UrlPreparer;

  /*
   * Standard constructor
   *
   * @param[in] spec Should remain valid over the lifetime of the instance.
   * @param[in] base_url The url that the spec paths are relative to.
   * @param[in] non_media_upload_path The path to send metadata to when
   *            there is no media content (i.e. the non-media upload case).
   *
   * TODO(user): In the future this will also be used if multipart is
   * not supported.
   */
  explicit MediaUploader(
      const MediaUploadSpec* spec,
      const StringPiece& base_url,
      const StringPiece& non_media_upload_path);

  /*
   * Standard destructor
   */
  virtual ~MediaUploader();

  /*
   * Find out if BuildRequest was called and returned successfully
   * so that the uploader is ready to Upload().
   *
   * @return true if can perform media upload, false if not yet ready.
   */
  bool is_ready() const { return ready_; }

  /*
   * Set custom multipart oundary separator.
   *
   * Overrides the boundary used for multipart separators should this instance
   * ever create a multipart content_type.
   *
   * If the boundary is empty then it will be automatically determined from
   * the content. Since the content reader may not be resettable, relying on
   * auto-boundaries might make the request unsendable if the content payload
   * cannot be reset between discovering the boundary and sending the message.
   */
  void set_multipart_boundary(const string& boundary) {
    multipart_boundary_ = boundary;
  }

  /*
   * Returns the separator string used to denote the multipart boundary.
   */
  const string& multipart_boundary() const { return multipart_boundary_; }

  /*
   * Set the metadata using a Json object or pre-encoded string.
   *
   * @param[in] from_json If this is empty then no metadata will be added
   *            (i.e. just a media upload). Otherwise this is a JSON-encoded
   *            metadata payload.
   */
  void set_metadata(const SerializableJson& from_json);

  /*
   * Set the metadata using a Json object or pre-encoded string.
   * @param[in] content_type The content type of the metadata payload.
   * @param[in] from_text The content_type encoded metadata payload.
   */
  void set_metadata(const string& content_type, const string& from_text);

  /*
   * Set the media content payload byte stream.
   *
   * @param[in] content_type The media payload content type.
   * @param[in] media Takes ownership of the mdeia payload byte stream.
   *
   * If the reader and content_type is empty then the uploader will just
   * send the metadata (if any) and ignore the content entirely.
   */
  void set_media_content_reader(const string& content_type, DataReader* media);

  /*
   * Returns the base_url bound in teh constructor.
   */
  const string& base_url() const { return base_url_; }

  /*
   * Returns the non_media_upload_path bound in the constructor.
   */
  const string& non_media_upload_path() const {
    return non_media_upload_path_;
  }

  /*
   * Synchronously perform the upload protocol using the given request.
   *
   * This method requires BuildRequest was called to fill out the request.
   * If authorization is needed, the credentials should be set on the request.
   *
   * @param[in,out] http_request The request used to perform the upload.
   * @return The overall status of the upload. Error detail can be retrieved
   *         from the http_request->status().
   *
   */
  googleapis::util::Status Upload(HttpRequest* http_request);

  /*
   * Asynchronously perform the upload protocol using the given request.
   *
   * This method requires BuildRequest was called to fill out the request.
   * If authorization is needed, the credentials should be set on the request.
   * If the request preparation fails request->WillNotExecute() will be called.
   *
   * @param[in,out] http_request The request used to perform the upload.
   *                Error detail can be retrieved with http_request->status().
   * @param[in] http_callback The Callback to be invoked when the operation
   *            is complete.
   */
  void UploadAsync(HttpRequest* request, HttpRequestCallback* callback);

  /*
   * prepares the uploader and builds the HttpRequest.
   *
   * This fills out the request body and any critical headers that the
   * underlying protocol uses. Any metadata and content attributes for
   * the uploader must be set before calling this method.
   *
   * This method also determines the final protocol and url endpoint to use.
   * The url_preparer is used to resolve variables in templated urls and add
   * any other query parameters. It is only called once so can be a single
   * use callback. It can be left NULL to not modify the protocol url, but
   * should only do so if there are known to be no templated parameters
   * in the urls or extra query parameters needed in the upload request.
   *
   * @param[in,out] request The request to build.
   * @param[in] url_preparer The Callback for resolving template variables
   *            in the upload url if it was specified using URI Templating..
   */
  googleapis::util::Status BuildRequest(HttpRequest* request, UrlPreparer* url_preparer);

 private:
  const MediaUploadSpec* spec_;  // bound in constructor
  string multipart_boundary_;    // when generating multipart payloads
  string base_url_;
  string non_media_upload_path_;

  // TODO(user): change this to a reader after adding reader to Json.
  string metadata_content_;
  string metadata_content_type_;

  std::unique_ptr<DataReader> media_content_reader_;
  string media_content_type_;

  bool ready_;  // Set true within BuildRequest

  /*
   * Helper function for BuildPayload when payload is a multipart form.
   * @param[out] content_type The content type for the reader returned.
   * @return Reader containing the payload for the HTTP upload request.
   */
  DataReader* CreateMultipartPayloadReader(string* content_type);

  DISALLOW_COPY_AND_ASSIGN(MediaUploader);
};

}  // namespace client

}  // namespace googleapis
#endif  // GOOGLEAPIS_SERVICE_MEDIA_UPLOADER_H_
